#include "physics/pworld.h"

#include <physics/pbullet.h>
#include <physics/pstar.h>
#include <physics/pplanet.h>

#include <cmath>

#include <QDomDocument>

PWorld::PWorld(double width, double height, QObject *parent) :
    QObject(parent)
{
    myWidth = width;
    myHeight = height;
}

void PWorld::addObject(PObject *object) {
    myObjects.append(object);
    if (object->getType() == "ship") {
        myShips.append((PShip*)object);
    }
}

void PWorld::delObject(PObject *object) {
    int i = myObjects.indexOf(object);
    if (object->getType() == "ship") {
        int j = myShips.indexOf((PShip*)object);
        if (j >= 0) {
            myShips.remove(j);
        }
    }
    if (i >= 0) {
        myObjects.remove(i);
        //object->willBeRemoved();
        //delete object;
        object->deleteLater();
    }
}

PShip *PWorld::getShip(QString name) const {
    foreach (PShip *ship, myShips) {
        if (ship->getName() == name) {
            return ship;
        }
    }
    return 0;
}


QVector<PObject*> PWorld::getObjects() const {
    return myObjects;
}

void PWorld::checkCollision(PObject *obj1, PObject *obj2) {
    if (obj1->distanceTo(obj2) <= obj1->getR() + obj2->getR()) {

        obj1->hit(obj2);
        if (obj1->getLife() <= 0) {
            delObject(obj1);
        }
        if (obj2->getLife() <= 0) {
            delObject(obj2);
        }
    }
}

void PWorld::processObject(PObject *obj1) {
    PVector a = 0;
    if (obj1->getLife() <= 0) {
        delObject(obj1);
        return;
    }
    foreach (PObject *obj2, myObjects) {
        if (obj2 == obj1) {
            continue;
        }
        checkCollision(obj1, obj2);
        PVector r = obj2->getPos() - obj1->getPos();
        PVector da = r * (G * obj2->getM() / (r.length() * r.length() * r.length()));
        a = a + da;
    }
    obj1->setA(a);
}

void PWorld::processShip(PShip *ship) {
    foreach (PObject *obj2, myObjects) {
        if (obj2 == ship) {
            continue;
        }
        PVector r = obj2->getPos() - ship->getPos();
        if (obj2->getBrightness() > 0) {
            ship->incEnergy(0.02 + obj2->getBrightness() / (r.length()));
        }
    }
    if (timeCycle % 2 == 0) {
        PVector da = 0;
        if (ship->isAccelerating()) {
            if (ship->getEnergy() > ship->getEnergyForEngine()) {
                double engine = ship->getEngine();
                da = PVector(engine * cos(ship->getAlpha()), engine * sin(ship->getAlpha()));
                ship->decEnergy(ship->getEnergyForEngine());
            }
        }
        if (ship->isLeft()) {
            ship->turnLeft();
        }
        if (ship->isRight()) {
            ship->turnRight();
        }
        if (ship->isFire() && timeCycle % 3 == 0) {
            if (ship->getEnergy() > ship->getEnergyForBullet()) {
                PBullet *bullet = new PBullet(this);
                double alpha = ship->getAlpha();
                PVector shipSpeed = ship->getV();
                PVector bulletSpeed = PVector(ship->getBulletSpeed() * cos(alpha), ship->getBulletSpeed() * sin(alpha));
                bullet->setPos(ship->getX() + (ship->getR() + 4) * cos(alpha), ship->getY() + (ship->getR() + 4) * sin(alpha));
                bullet->setV(shipSpeed + bulletSpeed);
                ship->setV(shipSpeed - bulletSpeed * (bullet->getM() / ship->getM()));
                addObject(bullet);
                ship->decEnergy(ship->getEnergyForBullet());
            }
        }
        ship->setA(ship->getA() + da);
    }
}

void PWorld::tick(double t) {
    timeCycle++;
    foreach (PObject *obj, myObjects) {
        double x = obj->getPos().getX();
        double y = obj->getPos().getY();
        if (x - obj->getR() < 0 || x + obj->getR() > myWidth) {
            obj->setV(-obj->getV().getX(), obj->getV().getY());
            obj->decLife(10 + 10 * abs(obj->getV().getX()));
        }
        if (y - obj->getR() < 0 || y + obj->getR() > myHeight) {
            obj->decLife(10 + 10 * abs(obj->getV().getY()));
            obj->setV(obj->getV().getX(), -obj->getV().getY());
        }
        processObject(obj);
        if (obj->getType() == "ship") {
            processShip((PShip *)obj);
        }
    }
    foreach (PObject *obj, myObjects) {
        obj->move(t);
    }
}

double PWorld::getWidth() const {
    return myWidth;
}

double PWorld::getHeight() const {
    return myHeight;
}

void PWorld::serialize(QDomDocument *doc, QDomElement *e) const {
    QDomElement elem = doc->createElement("World");

    QDomElement width = doc->createElement("Width");
    QDomText width_v = doc->createTextNode(QString("%1").arg(myWidth));
    width.appendChild(width_v);
    elem.appendChild(width);

    QDomElement height = doc->createElement("Height");
    QDomText height_v = doc->createTextNode(QString("%1").arg(myHeight));
    height.appendChild(height_v);
    elem.appendChild(height);

    QDomElement objects = doc->createElement("Objects");
    foreach (PObject *obj, myObjects) {
        QDomElement n = doc->createElement("Object");
        obj->serialize(doc, &n);
        objects.appendChild(n);
    }
    elem.appendChild(objects);

    e->appendChild(elem);
}

PWorld *PWorld::deserialize(QDomNode e, QObject *parent) {
    int height = 0;
    int width = 0;
    QDomNode node;

    QDomNode n = e.firstChild();
    while (!n.isNull()) {
        QString name = n.toElement().tagName();
        if (name == "Width") {
            width = n.firstChild().toText().data().toInt();
        } else if (name == "Height") {
            height = n.firstChild().toText().data().toInt();
        } else if (name == "Objects") {
            node = n.firstChild();
        }
        n = n.nextSibling();
    }
    PWorld *world = new PWorld(width, height, parent);
    while (!node.isNull()) {
        QDomElement elem = node.toElement();
        if (!elem.isNull()) {
            if (elem.tagName() == "Object") {
                QString type = elem.attribute("type");
                if (type == "Star") {
                    PStar *star = PStar::deserialize(&elem, world);
                    world->addObject(star);
                } else if (type == "Planet") {
                    PPlanet *planet = PPlanet::deserialize(&elem, world);
                    world->addObject(planet);
                } else if (type == "Bullet") {
                    PBullet *bullet = PBullet::deserialize(&elem, world);
                    world->addObject(bullet);
                } else if (type == "Ship") {
                    PShip *ship = PShip::deserialize(&elem, world);
                    world->addObject(ship);
                }
            }
        }
        node = node.nextSibling();
    }

    return world;
}
